package com.radar.visualize;

import cn.hutool.core.date.DateUtil;
import com.radar.algorithm.MathCommon;
import com.radar.common.Common;
import com.radar.common.DateConstants;
import com.radar.common.GisConstants;
import com.radar.common.ProductType;
import com.radar.exception.RadarCaclError;
import com.radar.exception.RadarDecodeError;
import com.radar.exception.RadarVisualizeError;
import com.radar.io.RadarGridData;
import com.radar.io.ShapeSourcePool;
import com.radar.io.cinrad.BaseCinrad;
import org.meteoinfo.data.GridData;
import org.meteoinfo.global.Extent;
import org.meteoinfo.global.MIMath;
import org.meteoinfo.global.PointD;
import org.meteoinfo.layer.VectorLayer;
import org.meteoinfo.shape.Shape;
import ucar.ma2.InvalidRangeException;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @author: fy-wbj
 * @description: draw simple raster layer
 * @date: 2020/9/26
 */
public class RasterMap extends ThematicMap {

    public RasterMap(BaseCinrad radar) {
        super(radar);
    }

    //region raster info

    /**
     * @author: fy-wbj
     * @description: draw raster radar info
     * @date: 2020/9/22
     */
    public static class RasterInfo {

        /**
         * radar elevation index
         */
        private int layers;

        /**
         * radar element
         */
        private ProductType type;

        /**
         * polar rings  (if null ? do not draw a circle : max range is rings[rings.length - 1])
         */
        private int[] rings = null;

        /**
         * if null ? Default background color blacs
         */
        private Color backColor = Color.black;

        /**
         * draw radar site detail on right
         */
        private boolean simpleDetail = false;

        /**
         * draw map city and name
         */
        private boolean drawCity = false;

        public RasterMap.RasterInfo rings(int[] rings) throws RadarVisualizeError {
            try {
                if (rings == null || rings.length == 0) {
                    throw new RadarVisualizeError("rings length must > 0");
                }
                if (rings.length > 2) {
                    for (int i = 1; i < rings.length; i++) {
                        if (rings[i] <= rings[i - 1]) {
                            throw new RadarVisualizeError("set rings error");
                        }
                    }
                }
            } catch (Exception e) {
                throw new RadarVisualizeError("set rings error");
            }

            this.rings = rings;
            return this;
        }

        public RasterMap.RasterInfo backColor(Color backColor) {
            this.backColor = backColor;
            return this;
        }

        public RasterMap.RasterInfo drawCity(boolean drawCity) {
            this.drawCity = drawCity;
            return this;
        }

        public RasterMap.RasterInfo simpleDetail(boolean simpleDetail) {
            this.simpleDetail = simpleDetail;
            return this;
        }

        public RasterMap.RasterInfo type(ProductType type) {
            this.type = type;
            return this;
        }

        public RasterMap.RasterInfo layers(int layers) {
            this.layers = layers;
            return this;
        }
    }

    public static RasterMap.RasterInfo buildRasterInfo(ProductType type) {
        RasterMap.RasterInfo rasterInfo = new RasterMap.RasterInfo();
        rasterInfo.type = type;
        return rasterInfo;
    }

    public BufferedImage simpleRaster(RasterMap.RasterInfo rasterInfo, RadarGridData data) throws RadarVisualizeError, RadarDecodeError {
        if(rasterInfo.type == ProductType.ET || rasterInfo.type == ProductType.VIL){
            data.data = MathCommon.decodeCal(data.caclData);
        }else{
            data.data = MathCommon.decode(data.radarData,rasterInfo.type);
        }
        // rings
        Extent extent = getExtent(data, rasterInfo);
        int width = (int) (extent.getWidth() / data.getXDelt()) + 1;
        int height = (int) (extent.getHeight() / data.getYDelt()) + 1;

        double scaleX = (width) / (extent.maxX - extent.minX);
        double scaleY = (height) / (extent.maxY - extent.minY);
        int xOffset = 0;
        int yOffset = 0;
        int rwidth = width;

        if (rasterInfo.simpleDetail) {
            width += width / 4;
        }
        BufferedImage br = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = br.createGraphics();
        try {
            g.setBackground(rasterInfo.backColor);
            g.clearRect(0, 0, width, height);
            g.setPaint(rasterInfo.backColor);
            g.drawOval(0, 0, width, height);
            for (int i = 0; i < data.getYNum(); i++) {
                for (int j = 0; j < data.getXNum(); j++) {
                    int xindex = (rwidth - data.getXNum()) / 2 + j;
                    int yindex = (height - data.getYNum()) / 2 + i;
                    if (xindex < 0 || yindex < 0) {
                        continue;
                    }
                    if (xindex > width - 1 || yindex > height - 1) {
                        continue;
                    }
                    if (!MIMath.doubleEquals(data.data[i][j], GisConstants.UNDEF)) {
                        if (j >= width || i >= height) {
                            continue;
                        }
                        Color color = getColor(data.data[i][j], rasterInfo.type);
                        if (color != null) {
                            br.setRGB(xindex, yindex, color.getRGB());
                        }
                    }
                }
            }
            Font font = new Font("Arial", Font.PLAIN, 12);
            g.setFont(font);
            g.setColor(new Color(255 - rasterInfo.backColor.getRed(), 255 - rasterInfo.backColor.getGreen(), 255 - rasterInfo.backColor.getBlue(), 128));
            // draw city map
            g.setColor(new Color(255 - rasterInfo.backColor.getRed(), 255 - rasterInfo.backColor.getGreen(), 255 - rasterInfo.backColor.getBlue(), 200));
            if (rasterInfo.drawCity) {
                drawCity(extent, g, scaleX, scaleY, br);
            }
            // draw rings
            if (rasterInfo.rings != null) {
                Stroke dash = new BasicStroke(1.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 2.5f, new float[]{10, 5,}, 0f);
                g.setStroke(dash);
                for (int i = 0; i < rasterInfo.rings.length; i++) {
                    Extent ringsExtent = Common.getExtentRadius(radar.getRadarSite().getLon(), radar.getRadarSite().getLat(), rasterInfo.rings[i] * 1000);
                    int[] mins = toScreen(ringsExtent.minX, ringsExtent.minY, extent.minX, extent.minY, scaleX, scaleY);
                    int[] maxs = toScreen(ringsExtent.maxX, ringsExtent.maxY, extent.minX, extent.minY, scaleX, scaleY);
                    g.drawArc(mins[0] + xOffset, mins[1] + yOffset, maxs[0] - mins[0], maxs[1] - mins[1], 0, 360);
                    g.drawString(String.format("%d km", rasterInfo.rings[i]), (maxs[0] + mins[0]) / 2 + xOffset, mins[1] + yOffset);
                }
                g.setStroke(new BasicStroke());
            }
            // draw simpleDetail
            if (rasterInfo.simpleDetail) {
                int x = rwidth + 20;
                int h = height / 4;
                int zh = h / 10;
                int y = zh;
                font = new Font("Arial", Font.PLAIN, zh);
                g.setFont(font);
                g.drawString(rasterInfo.type.toString(), x, y);
                y += zh;
                g.drawString(String.format("Resolution: %s °", simpleDoubleText(data.getXDelt())), x, y);
                y += zh;
                g.drawString(String.format("Date: %s", DateUtil.format(radar.dateTime, DateConstants.DATE_FORMAT_YYYY_MM_DD)), x, y);
                y += zh;
                g.drawString(String.format("Time: %s", DateUtil.format(radar.dateTime, "HH:mm")), x, y);
                y += zh;
                g.drawString(String.format("Radr: %s", radar.getRadarSite().getStationName()), x, y);
                y += zh;
                g.drawString(String.format("Task: VCP %s ", radar.vcp), x, y);
                y += zh;
                g.drawString(String.format("Evel: %s deg ", simpleDoubleText(radar.getElevation()[rasterInfo.layers])), x, y);
                y += zh;
                g.drawString(String.format("Lon: %s ", simpleDoubleText(radar.getRadarSite().getLon())), x, y);
                y += zh;
                g.drawString(String.format("Lat: %s ", simpleDoubleText(radar.getRadarSite().getLat())), x, y);
                y += zh;
                g.drawString(String.format("Alt: %s m", simpleDoubleText(radar.getRadarSite().getAlt())), x, y);
                y += zh;
                drawLegend(g, rasterInfo.type, x, height / 2, 30, br, new Color(255 - rasterInfo.backColor.getRed(), 255 - rasterInfo.backColor.getGreen(), 255 - rasterInfo.backColor.getBlue(), 200));
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RadarVisualizeError(e.getMessage());
        } finally {
            if (g != null) {
                g.dispose();
            }
        }
        return br;
    }

    //endregion

    //region CR
    public BufferedImage simpleCR(RasterMap.RasterInfo rasterInfo) throws InvalidRangeException, RadarDecodeError, RadarCaclError, IOException, RadarVisualizeError {
        RadarGridData data = this.radar.getCR();
        rasterInfo.type = ProductType.CR;
        return simpleRaster(rasterInfo, data);
    }

    public BufferedImage simpleGRAY(RasterMap.RasterInfo rasterInfo) throws InvalidRangeException, RadarDecodeError, RadarCaclError, IOException, RadarVisualizeError {
        RadarGridData data = this.radar.getCR();
        rasterInfo.type = ProductType.REFGRAY;
        return simpleRaster(rasterInfo, data);
    }
    //endregion

    //region ET
    public BufferedImage simpleET(RasterMap.RasterInfo rasterInfo) throws InvalidRangeException, RadarDecodeError, RadarCaclError, IOException, RadarVisualizeError {
        RadarGridData data = this.radar.getET();
        rasterInfo.type = ProductType.ET;
        return simpleRaster(rasterInfo, data);
    }
    //endregion

    //region VIL
    public BufferedImage simpleVIL(RasterMap.RasterInfo rasterInfo) throws InvalidRangeException, RadarDecodeError, RadarCaclError, IOException, RadarVisualizeError {
        RadarGridData data = this.radar.getVIL();
        rasterInfo.type = ProductType.VIL;
        return simpleRaster(rasterInfo, data);
    }
    //endregion

    //region ppi info

    public BufferedImage simplePPI(RasterMap.RasterInfo rasterInfo, int layers) throws IOException, RadarCaclError, InvalidRangeException, RadarDecodeError, RadarVisualizeError {
        RadarGridData data = this.radar.getPPI(layers, rasterInfo.type);
        rasterInfo.layers = layers;
        return simpleRaster(rasterInfo, data);
    }

    //endregion

    //region common

    public void drawCity(Extent extent, Graphics2D g, double scaleX, double scaleY, BufferedImage bufferedImage) throws Exception {
        VectorLayer city = ShapeSourcePool.getShapeCache("city");
        List name = city.getAttributeTable().getTable().getColumnData("NAME").getData();
        Set<String> names = new HashSet();
        for (int i = 0; i < city.getShapeNum(); i++) {
            Shape shape = city.getShape(i);
            if (shape.getExtent().minX > extent.maxX) {
                continue;
            }
            if (shape.getExtent().maxX < extent.minX) {
                continue;
            }
            if (shape.getExtent().minY > extent.maxY) {
                continue;
            }
            if (shape.getExtent().maxY < extent.minY) {
                continue;
            }
            List<? extends PointD> points = shape.getPoints();
            int[] xpoints = new int[points.size()];
            int[] ypoints = new int[points.size()];
            for (int m = 0; m < points.size(); m++) {
                int[] ps = toScreen(points.get(m).X, points.get(m).Y, extent.minX, extent.minY, scaleX, scaleY);
                xpoints[m] = ps[0];
                ypoints[m] = bufferedImage.getHeight() - 1 - ps[1];
            }
            int[] ps = toScreen((shape.getExtent().minX + shape.getExtent().maxX) / 2, (shape.getExtent().minY + shape.getExtent().maxY) / 2, extent.minX, extent.minY, scaleX, scaleY);
            if (!names.contains(name.get(i).toString())) {
                g.drawString(name.get(i).toString(), ps[0], bufferedImage.getHeight() - 1 - ps[1]);
                names.add(name.get(i).toString());
            }
            g.drawPolyline(xpoints, ypoints, xpoints.length);
        }
    }


    public Extent getExtent(GridData ppi, RasterMap.RasterInfo rasterInfo) throws RadarDecodeError {
        if (rasterInfo.rings == null || rasterInfo.rings.length == 0) {
            return ppi.getExtent();
        } else {
            Extent ringsExtent = Common.getExtentRadius(radar.getRadarSite().getLon(), radar.getRadarSite().getLat(), rasterInfo.rings[rasterInfo.rings.length - 1] * 1000);
            if (ringsExtent.minX < ppi.getXMin()) {
                return ringsExtent;
            }
            return ppi.getExtent();
        }
    }

    //endregion
}
